package controller.webservice.bootstrap;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;

import manager.BidMgr;
import manager.CartMgr;
import manager.CompletedCourseMgr;
import manager.CourseMgr;
import manager.CoursePreRequisiteMgr;
import manager.RoundMgr;
import manager.SectionMgr;
import manager.SectionStudentMgr;
import manager.StudentMgr;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

/**
 * Bootstraps system by reseting records in the database and loading new records
 * from the CSV files.
 * 
 * @author huangjm.2012, jaslin.wee.2012, sheryl.tay.2012, huiwensong.2012
 * 
 */

public class BootstrapCtrl {

	public static final String BOOTSTRAP_DATA_DIRECTORY = "/WEB-INF/classes/data/";
	public static final String BOOTSTRAP_DATA_ZIP_FILE = "data.zip";

	private static final String BID_CSV = "bid.csv";
	private static final String COURSE_COMPLETED_CSV = "course_completed.csv";
	private static final String COURSE_CSV = "course.csv";
	private static final String PREREQUISITE_CSV = "prerequisite.csv";
	private static final String SECTION_CSV = "section.csv";
	private static final String STUDENT_CSV = "student.csv";

	private ArrayList<CsvLoader> csvLoaders = new ArrayList<>(6);

	/**
	 * Constructor instantiates each CSV loader classes.
	 * 
	 * @param pathToBootstrapDataDirectory
	 * 
	 * @throws IOException
	 *             if any one of the CSV files cannot be read.
	 */
	public BootstrapCtrl(String pathToBootstrapDataDirectory)
			throws IOException {
		// add in order of processing
		csvLoaders.add(new StudentCsvLoader(pathToBootstrapDataDirectory
				+ STUDENT_CSV));
		csvLoaders.add(new CourseCsvLoader(pathToBootstrapDataDirectory
				+ COURSE_CSV));
		csvLoaders.add(new SectionCsvLoader(pathToBootstrapDataDirectory
				+ SECTION_CSV));
		csvLoaders.add(new PreRequisiteCsvLoader(pathToBootstrapDataDirectory
				+ PREREQUISITE_CSV));
		csvLoaders.add(new CompletedCoursesCsvLoader(
				pathToBootstrapDataDirectory + COURSE_COMPLETED_CSV));
		csvLoaders
				.add(new BidsCsvLoader(pathToBootstrapDataDirectory + BID_CSV));
	}

	/**
	 * Returns results of loading CSV files.
	 * 
	 * @return a JSONObject containing results of bootstrapping.
	 */
	public JSONObject dumpResult() {
		JSONObject result = new JSONObject();

		if (hasLoadingErrors()) {
			result.put("status", "error");
			result.put("num-record-loaded", combineNumRowsLoaded());
			result.put("error", combineRowErrors());
		} else {
			result.put("status", "success");
			result.put("num-record-loaded", combineNumRowsLoaded());
		}

		return result;
	}

	/**
	 * Resets database records and starts loading records from CSV files.
	 */
	public void startBootstrap() {
		// reset database
		new StudentMgr().reset();
		new CourseMgr().reset();
		new CoursePreRequisiteMgr().reset();
		new SectionMgr().reset();
		new CompletedCourseMgr().reset();
		new BidMgr().reset();
		new RoundMgr().reset();
		new CartMgr().reset();
		new SectionStudentMgr().reset();

		// start loading
		for (CsvLoader eachCsvLoader : csvLoaders) {
			eachCsvLoader.load();
		}
	}

	/**
	 * Combines the number of rows loaded information for all CSV files.
	 * 
	 * @return a JSON Array containing information of the number of rows loaded
	 *         for each CSV file.
	 */
	private JSONArray combineNumRowsLoaded() {

		ArrayList<CsvLoader> sortedCsvLoaders = newSortedCsvLoaders();

		JSONArray numRowsLoaded = new JSONArray();

		for (CsvLoader eachCsvLoader : sortedCsvLoaders) {
			JSONObject obj = new JSONObject();
			obj.put(eachCsvLoader.getFile(), eachCsvLoader.getNumRowsLoaded());
			numRowsLoaded.add(obj);
		}

		return numRowsLoaded;
	}

	public LinkedHashMap<String, Integer> getNumRowsLoaded() {
		LinkedHashMap<String, Integer> numRowsLoaded = new LinkedHashMap<>();

		ArrayList<CsvLoader> sortedCsvLoaders = newSortedCsvLoaders();

		for (CsvLoader eachCsvLoader : sortedCsvLoaders) {
			numRowsLoaded.put(eachCsvLoader.getFile(),
					eachCsvLoader.getNumRowsLoaded());
		}

		return numRowsLoaded;
	}

	/**
	 * Combines the row errors for all CSV files.
	 * 
	 * @return a JSON Array containing error messages for each line of each CSV
	 *         file.
	 */
	private JSONArray combineRowErrors() {
		ArrayList<CsvLoader> sortedCsvLoaders = newSortedCsvLoaders();

		JSONArray rowErrors = new JSONArray();

		for (CsvLoader eachCsvLoader : sortedCsvLoaders) {
			rowErrors.addAll(eachCsvLoader.getRowErrors());
		}

		return rowErrors;
	}

	public ArrayList<JSONObject> getRowErrors() {
		ArrayList<CsvLoader> sortedCsvLoaders = newSortedCsvLoaders();

		ArrayList<JSONObject> rowErrors = new ArrayList<>();

		for (CsvLoader eachCsvLoader : sortedCsvLoaders) {
			rowErrors.addAll(eachCsvLoader.getRowErrors());
		}

		return rowErrors;
	}

	/**
	 * Checks if there are any validation errors while loading CSV.
	 * 
	 * @return true if there are any validation errors while loading lines from
	 *         CSV files, false otherwise
	 */
	public boolean hasLoadingErrors() {
		for (CsvLoader eachCsvLoader : csvLoaders) {
			if (!eachCsvLoader.getRowErrors().isEmpty()) {
				return true;
			}
		}
		return false;
	}

	public String getStatus() {
		if(hasLoadingErrors()) {
			return "error";
		}
		
		return "success";
	}
	
	/**
	 * Sorts CSVLoader objects in alphabetical order of file names, e.g.
	 * bid.csv, course.csv, course_completed.csv, etc.
	 * 
	 * @return a new array list containing the sorted CSVLoader objects.
	 */
	private ArrayList<CsvLoader> newSortedCsvLoaders() {
		ArrayList<CsvLoader> clonedCsvLoaders = new ArrayList<>(csvLoaders);

		// sort csv loaders in alphabetical order of the file names
		Collections.sort(clonedCsvLoaders, new Comparator<CsvLoader>() {
			@Override
			public int compare(CsvLoader o1, CsvLoader o2) {
				return o1.getFile().compareToIgnoreCase(o2.getFile());
			}
		});

		return clonedCsvLoaders;
	}

}
